using System;
using System.Collections.Generic;
using CLELCore.Assembly.CLELInstructions;

namespace opt.Tools.Branches
{
	public class UpdateBranchesAfterNoInstMoving
	{
		private List<CLELInstruction> code;
		private Dictionary<Int32,Int32> branches;

		public UpdateBranchesAfterNoInstMoving(List<CLELInstruction> code)
		{
			if(code == null)
				throw new ArgumentException("Code cannot be null");
			this.code = code;
			branches = new Dictionary<Int32,Int32>();
			Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
			int i,len = code.Count;
			int code_offset = 0;
			for(i = 0;i < len;i++)
			{
				offsets.Add(code_offset,i);
				code_offset += code[i].Length;
			}

			int offset = 0;
			CLELInstruction inst;
			int inst_length;
			code_offset = 0;
			byte opcode;
			for(i = 0;i < len;i++)
			{
				inst = code[i];
				inst_length = code[i].Length;
				if(BranchUtilities.GetBranchOffset(inst,ref offset))
				{
					branches.Add(i,offsets[code_offset+offset+inst_length]);
				}
				code_offset += inst_length;
			}

			for(i = 0;i < len;i++)
			{
				inst = code[i];
				//upgrade all short branches to long branches
				//to make sure offsets will fit
				opcode = inst.Opcode;
				if(opcode == CLELOpcode.BRS)
					code[i] = new Br(0);
				else if(opcode == CLELOpcode.BRFALSES)
					code[i] = new Brfalse(0);
				else if(opcode == CLELOpcode.BRTRUES)
					code[i] = new Brtrue(0);
				else if(opcode == CLELOpcode.BEQS)
					code[i] = new Beq(0);
				else if(opcode == CLELOpcode.BGES)
					code[i] = new Bge(0);
				else if(opcode == CLELOpcode.BGTS)
					code[i] = new Bgt(0);
				else if(opcode == CLELOpcode.BLES)
					code[i] = new Ble(0);
				else if(opcode == CLELOpcode.BLTS)
					code[i] = new Blt(0);
				else if(opcode == CLELOpcode.BNEUNS)
					code[i] = new Bneun(0);
				else if(opcode == CLELOpcode.BGEUNS)
					code[i] = new Bgeun(0);
				else if(opcode == CLELOpcode.BGTUNS)
					code[i] = new Bgtun(0);
				else if(opcode == CLELOpcode.BLEUNS)
					code[i] = new Bleun(0);
				else if(opcode == CLELOpcode.BLTUNS)
					code[i] = new Bltun(0);
				else if(opcode == CLELOpcode.LEAVES)
					code[i] = new Leave(0);
			}
		}

		public void UpdateOffsets()
		{
			//TODO: CONVERT ALL BRANCHES TO THE LARGE ONE AND FIX BRANCH OFFSETS
			if(code == null)
				throw new ArgumentException("Code cannot be null");
			Dictionary<Int32,Int32> offsets = new Dictionary<Int32,Int32>();
			int i,len = code.Count;
			int code_offset = 0;
			for(i = 0;i < len;i++)
			{
				offsets.Add(i,code_offset);
				code_offset += code[i].Length;
			}

			int index_from;
			int index_to;
			int from_offset;
			int to_offset;
			int offset;
			byte opcode;
			CLELInstruction temp;
			foreach(KeyValuePair<Int32,Int32> key in branches)
			{
				index_from = key.Key;
				index_to = key.Value;
				from_offset = offsets[index_from];
				to_offset = offsets[index_to];
				offset = to_offset-from_offset-code[index_from].Length;
				//set branch at index_from to this offset
				opcode = code[index_from].Opcode;
				temp = null;
				//TODO: ADD REST OF LARGE BRANCH INSTRUCTIONS
				if(opcode == CLELOpcode.BR)
				{
					temp = new Br(offset);
				}
				else if(opcode == CLELOpcode.BGE)
				{
					temp = new Bge(offset);
				}
				else if(opcode == CLELOpcode.BLT)
				{
					temp = new Blt(offset);
				}
				else if(opcode == CLELOpcode.BEQ)
				{
					temp = new Beq(offset);
				}
				else if(opcode == CLELOpcode.BGT)
				{
					temp = new Bgt(offset);
				}
				else if(opcode == CLELOpcode.BRFALSE)
				{
					temp = new Brfalse(offset);
				}
				else if(opcode == CLELOpcode.BRTRUE)
				{
					temp = new Brtrue(offset);
				}
				else if(opcode == CLELOpcode.BLE)
				{
					temp = new Ble(offset);
				}
				else if(opcode == CLELOpcode.BLTUN)
				{
					temp = new Bltun(offset);
				}
				else if(opcode == CLELOpcode.BNEUN)
				{
					temp = new Bneun(offset);
				}
				else if(opcode == CLELOpcode.BLEUN)
				{
					temp = new Bleun(offset);
				}
				else if(opcode == CLELOpcode.BGTUN)
				{
					temp = new Bgtun(offset);
				}
				else if(opcode == CLELOpcode.LEAVE)
				{
					temp = new Leave(offset);
				}
				//if(temp == null)
				//	Console.WriteLine(opcode);
			
				code[index_from] = temp;
			}
		}
	}
}
